基于OpenCPU方案的BC26 NB模组开发总结

您所在的位置:网站首页 移远 二次开发 基于OpenCPU方案的BC26 NB模组开发总结

基于OpenCPU方案的BC26 NB模组开发总结

2024-07-15 15:58:26| 来源: 网络整理| 查看: 265

文章目录 一.概述二.模块简介三.编译流程1.开发环境搭建2.工程编译3.程序烧录 四.程序开发流程1.主任务定义2.系统资源初始化3.socket连接流程4.socket连接成功后,发送数据5.在执行初始化工作时,注册了socket接收函数,下面实现它6.dns域名解析 五.总结六. 感谢支持

一.概述

  BC26是一款典型的NB模组,支持OpenCPU开发方式,所谓OpenCPU开发方式指的就是模组本身的CPU资源对外部分开放,也就是基于模组本身的固件库提供的API接口做二次开发,以满足不同的业务需要,这样就可以将以往“CPU+模组“的开发方式简化为单”模组“的开发方式。节省了硬件资源,同时避免了CPU和模组之间的通信,提高了通信可靠性和性能。BC26模块就支持Opencpu的开发方案,其提供的API接口是基于FreeRTOS操作系统的。本文就详细介绍BC26模块的OpenCPU开发流程。

二.模块简介

  BC26 NB模块主要以串口的方式与外部通信,对外主要提供了两个uart串口,一个为主串口main uart,另一个为调试串口debug uart。主串口主要的作用有两个:一个是烧录应用程序和升级版本固件;另一个是AT指令的通信口。当我们使用opencpu方案做开发时,主串口不再作为外部AT指令的通信口而只作为程序的烧录口用。调试串口主要是用来打印一些调试信息。所以说串口是BC26模块对外提供的主要接口。   另外要介绍的就是模块的复位引脚,因为在给模块烧录程序(或固件)的时候需要使模块复位以做烧录同步。   支持的网络协议包括:UDP/ TCP/ LwM2M /MQTT/ SNTP/ CoAP*/ PPP*/ TLS*/ DTLS*/ HTTP*/ HTTPS*。本文主要是基于UDP和TCP协议完成socket编程。

三.编译流程 1.开发环境搭建

  首先我拿到的是移远官方的SDK开发包(V1.6版本):BC26_QuecOpen_NB1_SDK_V1.6,这里有一个小插曲,我最开始拿到的是V1.5版本,在开发过程中发现V1.5版本不支持DNS协议解析,后来分析发现是SDK版本的问题,升级到V1.6版本后就可以了。拿到这个SDK包之后在windows环境下使用代码编辑工具打开(如:source insight、vscode等都可以),我选用的是vscode编辑器。

2.工程编译

  SDK开发包中提供make编译工具链,我们只要修改它提供的Makefile文件,将我么自己的添加的工程文件路径放在殡仪目录下就可以了,主要是头文件路径、源文件路径、编译的C文件设置,如下图所示: 在这里插入图片描述 在修改好Makefile文件之后,直接打开SDK中的make工具,执行make new重新编译整个工程,执行make编译修改的文件,执行make clean清除工程编译痕迹。make编译工具如下所示: 在这里插入图片描述

3.程序烧录

  在我们使用make new命令编译好整个工程之后会在SDK的build/gcc目录下生成app_image_bin.cfg文件,这就是我们编译完成的目标文件,等会就是将这个文件烧录到BC26模块中,如下所示: 在这里插入图片描述 程序的烧录需要使用到SDK提供的QFlash烧录工具,这个工具在tools目录中,打开QFlash后的界面如下: 在这里插入图片描述   将BC26的主串口连接到电脑上,在QFlash工具中选中对应的端口号,选择烧录的波特率(大小都可,不过选择较高的波特率烧录速度会加快,这里选择460800)。设置好串口之后点击Load FW Files按钮选中之前我们编译号的目标文件app_image_bin.cfg(切记:这个目标文件存放发路径一定不能包含中文,否则会烧写失败!!!)。选中之后页面会加载出烧录所需的工程文件路径如上图所示。现在一切准备就绪,可以开始烧录了:点击start之后,系统会停留在等待复位的状态,此时我们开机或者复位都可以做烧录同步,烧录结束后会提示PASS并进度条满格为绿色。至此烧录工作完成。

四.程序开发流程

  BC26是基于FreeRTOS系统做二次开发的,在SDK中提供了多种示例,我是基于tcp和udp示例完成了socket通讯工作,以下结合程序简要阐述开发流程。

1.主任务定义

  在custom_task_cfg.h文件中创建task,这里我只创建一个主任务proc_main_task:

TASK_ITEM(proc_main_task,main_task_id,10*1024,DEFAULT_VALUE1,DEFAULT_VALUE2) //main task

proc_main_task函数原型:调用了net_task任务,也就是我们编写的主代码

void proc_main_task(s32 taskId) { net_task(); //网络任务 } 2.系统资源初始化

  完成socket网络通信所使用的系统资源主要包括:uart串口,socket网络,flash存储等。SDK已经给我们封装并提供好了各种API,我们只需要调用即可。初始化部分代码如下:

//被调主任务 void net_task(void) { ST_MSG msg; s32 ret; u8 read_ip[2]={0}; //读取ip的前两个字节,作为是否已经写入FLASH的标志 u32 bund =0; //波特率 u8 bund_byte[4]; //波特率临时变量 /*1.首先确认是否要对系统配置默认信息*/ Ql_Flash_Read(1,309,read_ip,2); //从FLASH中读取ip地址前两位 if(read_ip[0]==0 && read_ip[1]==0)Write_DefaultConfig_To_FLASH(); //如果FLASH为空,将系统默认配置信息写入FLASH //Write_DefaultConfig_To_FLASH(); /*2.从FLASH中读取波特率并保存到变量bund中*/ Ql_Flash_Read(1,309,bund_byte,4); //从FLASH中读取波特率 bund = bund_byte[0] | bund_byte[1] u8 srvport[10]; u8 *p = NULL; u8 pData[50]={0}; s32 ret; //1.command: Set_Srv_Param=, Ql_sprintf((char *)pData,"Set_Srv_Param=,\r\n",socekt_information->ip_address,socekt_information->port); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"Set_Srv_Param="); if (p) { Ql_memset(m_SrvADDR, 0, SRVADDR_LEN); if (Analyse_Command(pData, 1, '>', m_SrvADDR)) { //APP_DEBUG("SOCKET Address Parameter Error.\r\n"); return QL_NO; //SOCKET地址错误,返回NO } Ql_memset(srvport, 0, 10); if (Analyse_Command(pData, 2, '>', srvport)) { //APP_DEBUG("SOCKET Port Parameter Error.\r\n"); return QL_NO; //SOCKET端口错误,返回NO } socket_param_t.address = Ql_MEM_Alloc(sizeof(u8)*SRVADDR_LEN); if(socket_param_t.address!=NULL) { Ql_memset(socket_param_t.address,0,SRVADDR_LEN); Ql_memcpy(socket_param_t.address,m_SrvADDR,sizeof(m_SrvADDR)); } socket_param_t.remote_port= Ql_atoi(srvport); //APP_DEBUG("Set Server Parameter Successfully,\r\n",socket_param_t.address,socket_param_t.remote_port); } //2.command:SOCKET_ContextID= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"command:SOCKET_ContextID=\r\n",socekt_information->contextid); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ContextID="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET Contextid Parameter Error.\r\n"); return QL_NO; //SOCKET_ContextID错误,返回NO } socket_param_t.contextID = Ql_atoi(temp_buffer); //APP_DEBUG("Set Contextid Parameter Successfully\r\n",socket_param_t.contextID); } //3.command:SOCKET_ServiceType= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_ServiceType=\r\n",socekt_information->servicetype); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ServiceType="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET service type Parameter Error.\r\n"); return QL_NO; //SOCKET_Service类型错误,返回NO } socket_param_t.service_type = Ql_atoi(temp_buffer); //APP_DEBUG("Set service type Parameter Successfully\r\n",socket_param_t.service_type); } //4.command:SOCKET_LocalPort= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"command:SOCKET_LocalPort=\r\n",socekt_information->localport); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_LocalPort="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET local port Parameter Error.\r\n"); return QL_NO; //socket本地端口错误,返回NO } socket_param_t.local_port = Ql_atoi(temp_buffer); //APP_DEBUG("Set local port Parameter Successfully\r\n",socket_param_t.local_port); } //5.command:SOCKET_ProtocolType= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_ProtocolType=\r\n",socekt_information->protocoltype); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_ProtocolType="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET protocol type Parameter Error.\r\n"); return QL_NO; //protocol类型错误,返回NO } socket_param_t.protocol_type = Ql_atoi(temp_buffer); //APP_DEBUG("Set protocol type Parameter Successfully\r\n",socket_param_t.protocol_type); } //6.command:SOCKET_Open= Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零 Ql_sprintf((char *)pData,"SOCKET_Open=\r\n",socekt_information->connectid); //APP_DEBUG("%s\r\n",pData); p = Ql_strstr(pData,"SOCKET_Open="); if (p) { Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH); if (Analyse_Command(pData, 1, '>', temp_buffer)) { //APP_DEBUG("SOCKET Open Error.\r\n"); return QL_NO; //socket打开错误,返回NO } socket_param_t.connectID = Ql_atoi(temp_buffer); ret = RIL_SOC_QIOPEN(&socket_param_t); if(ret == 0) { //APP_DEBUG("\r\n",ret); } else { //APP_DEBUG("\r\n",ret); return QL_NO; //socket打开失败,返回NO } } return QL_OK; //全部配置成功,返回OK }

其中socekt_param_user_t结构体表示配置socket的相关参数,具体如下:

//定义该结构体表示socekt相关信息 typedef struct socekt_param_user { u8 *ip_address; //IP地址 u8 *port; //端口号 u8 contextid; //context ID, range is 1-3 u8 servicetype; // 选择服务类型 0: Start a TCP connection as a client,1: Start a UDP connection as a client u8 localport; //The local port, range is 1-65535,if is 0, then the local port will be assigned automatically, else the local port is assigned as specified u8 protocoltype; //0: IPv4,1: IPv6 u8 connectid; //socket service index, range is 0-4 }socekt_param_user_t; //结构体类型重定义

调用Connect_To_SOCEKT函数建立socket连接部分代码如下:

//初始化结构体参数,作为形参传入 socekt_information.ip_address=IP_address; //IP=47.110.249.116 socekt_information.port=portnum; //port=8005 socekt_information.contextid=1; //contextid=1 socekt_information.servicetype=service_type; //servicetype: 1--->UDP 0---->TCP socekt_information.localport=0; //localport=0: 0:自动分配 socekt_information.protocoltype=0; //protocoltype=0 0: IPv4,1: IPv6 socekt_information.connectid=0; //connectid=0 ret=Connect_To_SOCEKT(&socekt_information); //建立SOCEKT连接 Ql_Delay_ms(2000); //延时2S等待连接建立 if(ret==QL_OK) //判断返回是否成功 { APP_DEBUG("socket连接成功,进入透传模式\r\n"); //进入透传模式 } else{ APP_DEBUG("socket连接失败,请重新连接!!!\r\n"); //socket连接失败 } 4.socket连接成功后,发送数据

  在建立socket连接之后,模组进入透传模式,直接调用相关API函数发送数据,具体函数及调用如下:

/** * @brief UDP发送数据 * * @param data_buffer:数据缓存区首地址 * len: 数据长度 * connectid: socket service index, range is 0-4,根据上面Connect_To_UDP()函数确定 * data_type: Data_String表示发送的是String类型数据 * Data_Hex表示发送的是Hex类型数据 * * @return QL_NO 建立连接失败 * QL_OK 建立连接成功 */ u8 SendData_SOCEKT(u8 *data_buffer,u32 len,u8 connectid,u8 data_type) { s32 ret; //1.command: SOCKET_SENDDATA=, 发送String数据 if(data_type==Data_String) { ret = RIL_SOC_QISEND(connectid,len,data_buffer); if (ret == 0) { //APP_DEBUG("\r\n"); }else { //APP_DEBUG("\r\n",ret); return QL_NO; } } //2.command: SOCKET_SENDDATAHEX=, 发送HEX串数据 else if(data_type==Data_Hex) { ret = RIL_SOC_QISENDEX(connectid,len/2,data_buffer); if (ret == 0) { //APP_DEBUG("\r\n"); }else { //APP_DEBUG("\r\n",ret); return QL_NO; } } return QL_OK; //发送成功,返回QL_OK }

将串口接收到的数据直接通过模组透传出去:

Ql_memset(send_buffer, 0, sizeof(send_buffer)); //清空串口接收缓存区 HexArrayToString(pData,send_buffer,len); //将数据封装到send_buffer里面,数据长度扩大一倍 //APP_DEBUG("send_buffer=%s ,len=%d\r\n",send_buffer,len); SendData_SOCEKT(send_buffer,len*2,socekt_information.connectid,Data_Hex); //发送数据HEX,最大为512字节

在透传模式下发送"+++"使模块退出透传模式。

5.在执行初始化工作时,注册了socket接收函数,下面实现它

  在recv信息中包含相关头信息,这不是透传的信息,因此先使用指针操作去掉头部信息,只留下网络传回的的透传信息并copy到socket_recv_buffer缓存区中。然后直接通过串口传出,至此完成了一个完整的信息透传回路。

void callback_socket_recv(u8* buffer,u32 length) { u8 *p = NULL; u16 recv_len=0; //定义接收数据长度变量 //APP_DEBUG("\r\n",buffer,length); //原始数据 p = Ql_strstr(buffer,"0,"); //获取p指针地址 if(*(p+3)==',') { recv_len=Ql_atoi((p+2)); //获得接收字节数 Ql_memcpy(socket_recv_buffer,p+4,recv_len); } else { recv_len=Ql_atoi((p+2)); Ql_memcpy(socket_recv_buffer,p+5,recv_len); } //APP_DEBUG("%s",socket_recv_buffer); Ql_UART_Write(UART_PORT0, socket_recv_buffer, recv_len ); Ql_memset(socket_recv_buffer, 0, sizeof(socket_recv_buffer)); } 6.dns域名解析

  有些时候我们需要进行dns域名解析,同样系统给我们提供了API函数,但是dns解析需要花费一定的时间,因此我们开启一个定时器做判断dns是否解析成功。

/** * @brief 域名解析函数: * * @param *hostname:域名 * * @return void */ void get_ip_by_hostname(u8 *hostname) { //APP_DEBUG("hostname=%s\r\n",hostname); //输出服务器域名 Ql_IpHelper_GetIPByHostName(0, hostname, Callback_GetIpByName); //进行域名解析 //开启一个定时器 Ql_Timer_Register(UDP_TIMER_ID, Callback_Timer, NULL); Ql_Timer_Start(UDP_TIMER_ID, UDP_TIMER_PERIOD, TRUE); } static u8 m_ipaddress[IP_ADDR_LEN]; //存储DNS解析完成的IP值 //域名解析回调函数,在此函数中完成dns域名解析工作,并将最终解析出的ip地址存储在全局缓存区m_ipaddress中 void Callback_GetIpByName(u8 contexId,s32 errCode,u32 ipAddrCnt,u8* ipAddr) { if (errCode == SOC_SUCCESS) { Ql_memset(m_ipaddress, 0, IP_ADDR_LEN); Ql_memcpy(m_ipaddress, ipAddr, IP_ADDR_LEN); //APP_DEBUG("\r\n", __func__, contexId,errCode,ipAddrCnt,m_ipaddress); success_flag=1; //DNS解析成功标志置位 } } //定时器中断服务函数,在其中来判断是否dns解析成功并执行相应的动作 static void Callback_Timer(u32 timerId, void* param) { Ql_Timer_Stop(UDP_TIMER_ID); //停止定时器 if(success_flag) { success_flag=0; //DNS解析成功标志复位 APP_DEBUG("CONFIG_CENTER0 SUCCESS,DNS IP ADDRESS IS:%s\r\n",m_ipaddress); } else { APP_DEBUG("dns failed!!!\r\n"); } } 五.总结

  上述详细介绍了基于OpenCPU方案的BC26开发流程,其中开发的关键在于学会使用SDK提供的开发工具套件以及基于SDK提供的example开发自己的程序。上述并未给出完整的程序,只是针对一些关键点给出操作代码,最终的实现的功能为一个透传模块,根据主串口的命令进入透传模式并发送透传数据,发送+++退出透传模式。

六. 感谢支持

    完结撒花!希望看到这里的小伙伴能点个关注,我后续会持续更新,也欢迎大家广泛交流。     码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭